/**
 * \file	server/catalog.c*/


#include "common/sockets.h"
#include "common/server_loader_protocol.h"
#include "common/question.h"
#include "catalog.h"
#include "clientthread.h"

/* Globable Variablen für Shared Memory und Frageverwaltung  */

Question *shmem;	/**< Pointer auf Shared Memory */

size_t valueShm;	/**< Größe des gesamten Shared Memory */

pid_t pid;	/**< Prozess ID von Loader */

/*Pipes für Kommunikation mit Loader */
int pipeLoaderServerStd[2];
int pipeServerLoader[2];

/* String Arrays für Verarbeitung der Katalogliste */
char * ausgabe = NULL;
char * ausgabeRealloc;
char ** fileListe = NULL;
char ** listeRealloc;

/* Streams für Kommunikation über Pipes */
FILE *leseStream;
FILE *schreibeStream;
FILE *errstream;

bool debugGlobal = false;	/**< Debugmodus an Loader weiterreichen */

int anzahlQuestion = 0;	/**< Anzahl der geladenen Fragen */

/*	Loader 'Load' Befehl auslösen */

int ladeLoader(char* filename,	char* errorstring) {

	// Rueckgabestring von Loader
	char errormsg[50] = { 0 };

	// Befehl an Loader senden
	fputs("LOAD ", schreibeStream);
	// Filename = ausgewählter Katalog
	fputs(filename, schreibeStream);
	// Loader-Zeichen für Katalogende
	fputs("\n", schreibeStream);
	// Stream schreiben
	fflush(schreibeStream);

	// Antwort von Loader empfangen
	if (fgets(errormsg, 50, leseStream) != NULL) {

		//bei Fehler
		if (strncmp(errormsg, LOAD_ERROR_PREFIX, strlen(LOAD_ERROR_PREFIX))
				== 0) {

			// Lösche Error-Prefix
			memmove(errormsg, errormsg + strlen(LOAD_ERROR_PREFIX), 50);

			// Unterscheide Error-Typen und schreibe passende Nachricht in Error-String von Client-Thread
			if (strncmp(errormsg, LOAD_ERROR_CANNOT_OPEN, strlen(
					LOAD_ERROR_CANNOT_OPEN)) == 0) {

				strcpy(errorstring, "Katalog konnte nicht geöffnet werden.");
			}

			if (strncmp(errormsg, LOAD_ERROR_CANNOT_READ, strlen(
					LOAD_ERROR_CANNOT_READ)) == 0) {

				strcpy(errorstring, "Katalog konnte nicht gelesen werden.");
			}

			if (strncmp(errormsg, LOAD_ERROR_INVALID,
					strlen(LOAD_ERROR_INVALID)) == 0) {

				strcpy(errorstring, "Katalogformat ist ungültig.");
			}

			if (strncmp(errormsg, LOAD_ERROR_SHMEM, strlen(LOAD_ERROR_SHMEM))
					== 0) {

				strcpy(errorstring, "Interner Server-Fehler: shmem!");
			}

			if (strncmp(errormsg, LOAD_ERROR_OOM, strlen(LOAD_ERROR_OOM)) == 0) {

				strcpy(errorstring, "Interner Server-Fehler: oom!");
			}
		}

		//bei Erfolg: Anzahl der Fragen zurückgeben
		if (strncmp(errormsg, LOAD_SUCCESS_PREFIX, sizeof(LOAD_SUCCESS_PREFIX)
				- 1) == 0) {

			// Lösche Prefix
			memmove(errormsg, errormsg + sizeof(LOAD_SUCCESS_PREFIX) - 1, 50);

			// Bei Erfolg: Gebe Anzahl der geladenen Fragen als INTEGER an Client-Thread zurück

			anzahlQuestion = atoi(errormsg);

			return anzahlQuestion;
		}
	}

	// Bei Fehlerfällen
	return -1;
}

/*  Hole Liste der Fragekataloge von Loader*/

/*	Loader 'Browse' Befehl auslösen*/

char ** loaderBrowse() {

	// Zähler für einzelnen Char
	unsigned int count = 0;

	// Zähler für gelesenen Char oder Katalog
	int ch;

	// Pointer auf einzelnen Katalog
	char * pch;

	char oldchar = '\0';

	debugPrint("Versuche Browse auszulösen ...\n");

	fputs("BROWSE\n", schreibeStream);

	fflush(schreibeStream);

	debugPrint("Browse geschrieben!\n");

	debugPrint("Lese Dateinamen ein ...\n");

	while ((ch = getc( leseStream )) != EOF) { //Für jedes Zeichen dass vom Loader auf der Konsole ausgegeben wird (gepiped hier in den Server)

		ausgabeRealloc = (char*) realloc(ausgabe, count + 1); //einen neues char allocieren

		if (ausgabeRealloc != NULL) {

			ausgabe = ausgabeRealloc; //&dass übergebene Zeichen dort abspeichern

			ausgabe[count] = (char) ch;

			if (ausgabe[0] == '\n') {
				return NULL;
			}

		} else {

			free(ausgabe); //Falls dass allocieren fehlschlägt

			debugPrint("Fehler beim reallocieren!");

			exit(255);
		}

		count++; //Count zählt die bereits übergebenen Zeichen

		if (oldchar == '\n' && ch == '\n') {

			break;
		}

		oldchar = ch;
	}

	memset(ausgabe + count, 0, 1); // Füge Ende-Markierung ein

	count = 0;

	pch = strtok(ausgabe, "\n"); // Gesamtstring bei Newline teilen in seperate Strings

	//Da wir nun ein riesiges Array vom Loader bekommen haben müssen wir es noch splitten und zwar an jedem Zeilenumbruch
	while (pch != NULL) {

		listeRealloc
				= (char**) realloc(fileListe, count * sizeof(char*) + 1); //Für jede Zeile einen doppelten Charpointer allocieren

		if (fileListe != NULL) {

			fileListe = listeRealloc;

			fileListe[count] = (char*) malloc(strlen(pch) + 1); //Außerdem allocieren wir für jede Zeile den nötigen speicher und richten den entsprechenden iterator (count) auf dass neu allocierte array

			strcpy(fileListe[count], pch); //Der String aus der großen Zeichenkette wird in den eben allocierten Bereich kopiert.
		}

		debugPrint("%s", fileListe[count]);

		pch = strtok(NULL, "\n");

		count++;
	}

	// Ende Markierung
	fileListe[count + 1] = (char*) malloc(1);
	fileListe[count + 1] = '\0';

	fflush(stdout);

	debugPrint("Lesen abgeschlossen\n");

	return (fileListe);
}

/*	Startet den Loader */

int starteLoader(char* kat, bool debug) {

	// Erzeuge Pipe für Loader -> Server
	if (pipe(pipeLoaderServerStd) != 0) {
		perror("pipe:");
		exit(255);
	}

	// Erzeuge Pipe für Server->Loader
	if (pipe(pipeServerLoader) != 0) {
		perror("pipe:");
		exit(255);
	}

	// Öffne Streams auf Pipes
	if ((schreibeStream = fdopen(pipeServerLoader[1], "w")) == NULL) {
		perror("stream pipe:");
		exit(255);
	}
	if ((leseStream = fdopen(pipeLoaderServerStd[0], "r")) == NULL) {
		perror("stream pipe:");
		exit(255);
	}

	// Prozess abspalten
	pid = fork();

	// Fehler abfangen
	if (pid < 0) {
		perror("fork:");
		exit(255);

	} else

	// Kindprozess
	if (pid == 0) {

		dup2(pipeServerLoader[0], STDIN_FILENO); //Lesendes Ende auf Standart eingabe
		dup2(pipeLoaderServerStd[1], STDOUT_FILENO); //Ausgabe it pipe umleiten

		/* überflüssige Pipe-discreptoren schließen */
		close(pipeServerLoader[0]);
		close(pipeServerLoader[1]);
		close(pipeLoaderServerStd[0]);
		close(pipeLoaderServerStd[1]);

		/*Neues Programm in die Arbeitsumgebung laden*/
		if (debug == true) {

			debugGlobal = debug;

			if (execlp("./loader", "./loader", kat, "-d", NULL) == -1) {
				perror("exec loader:");
			}

		} else {

			if (execlp("./loader", "./loader", kat, NULL) == -1) {
				perror("exec loader:");
			}
		}

	} else

	//Im Elternprozess
	if (pid > 0) {

		close(pipeServerLoader[0]);
		close(pipeLoaderServerStd[1]);

		char** catalogs = loaderBrowse();

		if (catalogs == NULL) {
			return (-1);
		} else {
			setCatalogs(catalogs);
		}
	}

	// Stream leeren
	fflush(stdout);

	return (0);
}

/*  Beendet den Loader und gibt reservierte Ressourcen wieder frei */

int beendeLoader() {

	// Gebe Speicher für Katalogliste frei
	free(ausgabeRealloc);
	free(fileListe);

	// Schliesse Streams
	fclose(schreibeStream);
	fclose(leseStream);

	// Shared Memory Adressraum freigeben
	if (munmap(shmem, valueShm) == -1) {
		perror("SharedMem UnMap:");
	}
	/*Beendet den Loader und gibt reservierte Ressourcen wieder frei */

	// Shared Memory freigeben
	if (shm_unlink(SHMEM_NAME) == -1) {
		perror("SharedMem UnLink:");
	}

	return 0;
}

/*Öffne und Mappe Shared Memory */


/*Öffnet den Shared Memory lesend für den Server*/

int oeffneSharedMem(int number) {

	int shm;

	valueShm = number * sizeof(Question);

	shm = shm_open(SHMEM_NAME, O_RDONLY, 0);

	if (shm == -1) {

		switch (errno) {

		case EEXIST:
			errorPrint("Shared Memory wurde noch nicht angelegt.");
			break;

		default:
			errorPrint("Fehler beim öffnen des Shared Memorys: %s", strerror(
					errno));
			break;
		}

		return -1;
	}

	shmem = mmap(NULL, valueShm, PROT_READ, MAP_SHARED, shm, 0);

	if (shmem == MAP_FAILED) {

		errorPrint("Kann Shared Memory nicht in Adressraum einbinden: %s",
				strerror(errno));
		close(shm);

		shm_unlink(SHMEM_NAME);

		return -1;
	}

	return 0;
}

/*Liest eine Frage an der angebenen Poisition aus dem Shared Memory aus.*/

Question* getQuestion(int* position) {

	if (*position == anzahlQuestion) {
		return NULL;
	}

	Question* tmpPtr = shmem + *position;

	return tmpPtr;
}

